/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the "License").  You may not use this file except
 * in compliance with the License.
 * 
 * You can obtain a copy of the license at
 * https://jwsdp.dev.java.net/CDDLv1.0.html
 * See the License for the specific language governing
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL
 * HEADER in each file and include the License file at
 * https://jwsdp.dev.java.net/CDDLv1.0.html  If applicable,
 * add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your
 * own identifying information: Portions Copyright [yyyy]
 * [name of copyright owner]
 */
package com.probiz.estore.eai.common;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Enumeration;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.UnmarshallerHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.NamespaceSupport;
import org.xml.sax.helpers.XMLFilterImpl;

import com.probiz.estore.core.util.GenericsUtils;
import com.probiz.estore.core.util.I18nUtil;
import com.probiz.estore.eai.exception.EaiException;

/**
 * XML文件导入泛型基类  --  单业务实体载入内存One by One
 * 
 * 继承XMLFilter用以监测SAX事件，以便加载单个的业务实体进行处理，而不是对XML文件中的所有业务实体一次加载
 * 
 * 适用于XML文件中业务实体多时，如商品（商品XML文件中包含有多个商品，一个商品我们称为一个业务实体）、订单、客户模块。
 * 
 * @see GenericUnmarshaller  GenericUnmarshaller是一次加载XML文件中的所有业务实体到内存
 * 
 * @author pengzhirong
 */
public abstract class GenericPartialUnmarshaller<T> extends XMLFilterImpl {
    
	protected final transient Log	logger	= LogFactory.getLog(getClass());
	
	protected Class<T> entityClass = null;
	
	protected EaiUtil eaiUtil = null;
	
    private JAXBContext jaxbContext;
    
    private XMLReader xmlReader;
	
	public GenericPartialUnmarshaller() {
		entityClass = GenericsUtils.getSuperClassGenricType(getClass());
		
		try {
			jaxbContext = JAXBContext.newInstance(entityClass.getPackage().getName());
		} catch (JAXBException e) {
			e.printStackTrace();
		}
		
		// create a new XML parser
		SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setNamespaceAware(true);
        XMLReader reader = null;
		try {
			reader = factory.newSAXParser().getXMLReader();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
        
        // connect two components
        reader.setContentHandler(this);
        this.xmlReader = reader;
		
    }
	
	/**
	 * 要处理的目标XML文件全路径，即要导入的xml文件的全路径
	 * @return
	 */
	protected String getTargetXmlFilePath(){
		return eaiUtil.getTargetXmlFilePathByEntityClass(entityClass, false);
	}
	
	/**
	 * 得到最新的要处理的xml文件全路径
	 * @return
	 */
	private String getLatestTargetXmlFilePath(){
		
		String targetXmlFilePath = getTargetXmlFilePath();
		
		String latestTargetXmlFilePath = eaiUtil.getLatestTargetXmlFilePath(targetXmlFilePath);
			
		return latestTargetXmlFilePath;
	}
	
	/**
	 * 开始导入数据
	 */
	public void startUnmarshal() throws EaiException{
		File xmlFile = new File(getLatestTargetXmlFilePath());
		//同步文件不存在
		if(!xmlFile.exists()){
			logger.info("startUnmarshal():"+I18nUtil.getInstance().getMessage("eaiJobLog.info.fileNotFound", new Object[]{xmlFile.getAbsolutePath()}));
			return;
		}
		
		try {
			xmlReader.parse(xmlFile.toURL().toExternalForm());
		} catch (MalformedURLException e) {
			throw new EaiException(I18nUtil.getInstance().getMessage("eaiJobLog.exception.latestTargetXmlFilePath"), e);
		} catch (IOException e) {
			throw new EaiException(I18nUtil.getInstance().getMessage("eaiJobLog.exception.io"), e);
		} catch (SAXException e) {
			throw new EaiException(I18nUtil.getInstance().getMessage("eaiJobLog.exception.xml"), e);
		}
	}
    
    public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
        throws SAXException {
            
        if( depth!= 0 ) {
            // we are in the middle of forwarding events.
            // continue to do so.
            depth++;
            super.startElement(namespaceURI, localName, qName, atts);
            return;
        }
        
        //当标签名为泛型基类的名称时
        if( namespaceURI.equals("") && localName.equalsIgnoreCase(entityClass.getSimpleName()) ) {
            // start a new unmarshaller
            Unmarshaller unmarshaller;
            try {
                unmarshaller = jaxbContext.createUnmarshaller();
            } catch( JAXBException e ) {
                // there's no way to recover from this error.
                // we will abort the processing.
                throw new SAXException(e);
            }
            unmarshallerHandler = unmarshaller.getUnmarshallerHandler();
            
            // set it as the content handler so that it will receive
            // SAX events from now on.
            setContentHandler(unmarshallerHandler);
            
            // fire SAX events to emulate the start of a new document.
            unmarshallerHandler.startDocument();
            unmarshallerHandler.setDocumentLocator(locator);
            
            Enumeration e = namespaces.getPrefixes();
            while( e.hasMoreElements() ) {
                String prefix = (String)e.nextElement();
                String uri = namespaces.getURI(prefix);
                
                unmarshallerHandler.startPrefixMapping(prefix,uri);
            }
            String defaultURI = namespaces.getURI("");
            if( defaultURI!=null )
                unmarshallerHandler.startPrefixMapping("",defaultURI);

            super.startElement(namespaceURI, localName, qName, atts);
            
            // count the depth of elements and we will know when to stop.
            depth=1;
        }
    }
    
    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        
        // forward this event
        super.endElement(namespaceURI, localName, qName);
        
        if( depth!=0 ) {
            depth--;
            if( depth==0 ) {
                // just finished sending one chunk.
                
                // emulate the end of a document.
                Enumeration e = namespaces.getPrefixes();
                while( e.hasMoreElements() ) {
                    String prefix = (String)e.nextElement();
                    unmarshallerHandler.endPrefixMapping(prefix);
                }
                String defaultURI = namespaces.getURI("");
                if( defaultURI!=null )
                    unmarshallerHandler.endPrefixMapping("");
                unmarshallerHandler.endDocument();
                
                // stop forwarding events by setting a dummy handler.
                // XMLFilter doesn't accept null, so we have to give it something,
                // hence a DefaultHandler, which does nothing.
                setContentHandler(new DefaultHandler());
                
                // then retrieve the fully unmarshalled object
                JAXBElement<T> result = null;
                try {
                    result = (JAXBElement<T>)unmarshallerHandler.getResult();
                    
                } catch( JAXBException je ) {
                    logger.error("Unable unmarshal this file!");
                    //TODO 增加信息量 增加到同步日志
                    return;
                }
                
                //处理XML文件中的单个业务实体
                try {
                	processEntity(result.getValue());
				} catch (Exception e1) {
					logger.error("Entity process exception:"+e1.getMessage()+"!");
					//TODO 增加信息量 增加到同步日志
				}
                
                unmarshallerHandler = null;
            }
        }
    }
    
    public abstract void processEntity( T entity ) throws Exception;
    
    /**
     * Remembers the depth of the elements as we forward
     * SAX events to a JAXB unmarshaller.
     */
    private int depth;
    
    /**
     * Reference to the unmarshaller which is unmarshalling
     * an object.
     */
    private UnmarshallerHandler unmarshallerHandler;


    /**
     * Keeps a reference to the locator object so that we can later
     * pass it to a JAXB unmarshaller.
     */
    private Locator locator;
    public void setDocumentLocator(Locator locator) {
        super.setDocumentLocator(locator);
        this.locator = locator;
    }


    /**
     * Used to keep track of in-scope namespace bindings.
     * 
     * For JAXB unmarshaller to correctly unmarshal documents, it needs
     * to know all the effective namespace declarations.
     */
    private NamespaceSupport namespaces = new NamespaceSupport();
    
    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        namespaces.pushContext();
        namespaces.declarePrefix(prefix,uri);
        
        super.startPrefixMapping(prefix, uri);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        namespaces.popContext();
        
        super.endPrefixMapping(prefix);
    }

	public void setEaiUtil(EaiUtil eaiUtil) {
		this.eaiUtil = eaiUtil;
	}
}
